-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local signal = require 'mc.signal'
local base_msg = require 'messages.base'
local custom_msg = require 'messages.custom'
local utils_core = require 'utils.core'
local responder = require 'domain.responder'

local NONCE_REGEX = "^[a-fA-F0-9]{64}$"

local responder_collection = class()

function responder_collection:ctor(spdm_c)
    self.m_collection = {}
    self.spdm_c = spdm_c
end

function responder_collection:init()
    -- 信号注册
    self.m_component_added = signal.new()
    self.m_integrity_added = signal.new()
    self.m_measurement_added = signal.new()
    self.m_cert_added = signal.new()
end

function responder_collection:new_responder(chassis_id, device)
    if not self.m_collection[chassis_id] then
        self.m_collection[chassis_id] = {}
    end

    if self.m_collection[chassis_id][device.device_name] then
        log:error("responder existed, can not create")
        error(base_msg.InternalError())
    end

    self.m_collection[chassis_id][device.device_name] = responder.new(chassis_id, device)
end

function responder_collection:new_component(chassis_id, device, inventory_path, type)
    local obj = self.m_collection[chassis_id][device.device_name]
    if not obj then
        log:error("no responder, create trusted component failed")
        error(base_msg.InternalError())
    end

    self.spdm_c:new_responder(device.device_name, inventory_path, type)
    obj:new_component(chassis_id, device, inventory_path, type)
    self.m_component_added:emit(obj.m_component)
end

function responder_collection:add_certificate(chassis_id, device_name, slot_id, cert_info)
    local obj = self.m_collection[chassis_id][device_name]
    if not obj or not obj.m_component then
        log:error("no responder or trusted component, add certificate failed")
        error(base_msg.InternalError())
    end

    local cert_obj = obj.m_component:add_certificate(slot_id, cert_info)
    self.m_cert_added:emit(slot_id, cert_obj)
end

function responder_collection:new_integrity(chassis_id, device_name, integrity_info, measurement_info)
    local obj = self.m_collection[chassis_id][device_name]
    if not obj then
        log:error("no responder, create component integrity failed")
        error(base_msg.InternalError())
    end

    local id = obj:new_integrity(integrity_info, measurement_info)
    self.m_integrity_added:emit(obj.m_integrity_collection[id])

    return id
end

function responder_collection:add_measurement(chassis_id, device_name, id, measurement)
    local obj = self.m_collection[chassis_id][device_name]
    if not obj or not obj.m_integrity_collection[id] then
        log:error("no responder or integrity, add measurement failed")
        error(base_msg.InternalError())
    end

    obj.m_integrity_collection[id]:add_measurement(measurement)
    local mea_obj = obj:get_measurement(id, measurement.index)
    if not mea_obj then
        log:error("get measurement object failed")
        error(base_msg.InternalError())
    end
    self.m_measurement_added:emit(id, mea_obj)
end

function responder_collection:get_responder_by_mdb_name(chassis_id, mdb_name)
    if not self.m_collection[chassis_id] then
        return nil
    end

    for _, v in pairs(self.m_collection[chassis_id]) do
        if v.m_mdb_name == mdb_name then
            return v
        end
    end

    return nil
end

function responder_collection:get_signed_measurement(ctx, chassis_id, mdb_name, integrity_id, measurement_indices,
    slot_id, nonce)
    local responder_obj = self:get_responder_by_mdb_name(chassis_id, mdb_name)
    if not responder_obj then
        log:error("invalid parameter, cannot get responder")
        log:operation(ctx:get_initiator(), "spdm", "Get " .. mdb_name .. " Signed Measurements by SPDM failed")
        error(base_msg.ResourceMissingAtURI(""))
    end

    if not responder_obj.m_integrity_collection[integrity_id] then
        log:error("invalid parameter, cannot get component integrity")
        log:operation(ctx:get_initiator(), "spdm", "Get " .. mdb_name .. " Signed Measurements by SPDM failed")
        error(base_msg.ResourceMissingAtURI(""))
    end

    if not utils_core.g_regex_match(NONCE_REGEX, nonce) then
        log:error("nonce format error")
        log:operation(ctx:get_initiator(), "spdm", "Get " .. mdb_name .. " Signed Measurements by SPDM failed")
        error(base_msg.PropertyValueFormatError("******", "nonce"))
    end

    local ok, result = pcall(function()
        return self.spdm_c:get_signed_measurement(responder_obj.m_device_name, measurement_indices, slot_id, nonce)
    end)

    if not ok then
        log:error("get signed measurements failed, %s", result)
        log:operation(ctx:get_initiator(), "spdm", "Get " .. mdb_name .. " Signed Measurements by SPDM failed")
        error(custom_msg.OperationFailed())
    end

    log:operation(ctx:get_initiator(), "spdm", "Get " .. mdb_name .. " Signed Measurements by SPDM successful")
    return result.version, result.publicKey, result.hashAlgorithm, result.signAlgorithm, result.measurement
end

return singleton(responder_collection)